﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace JMacia.Gaming.d20.Core.Traits
{
    /// <summary>
    /// Represents a generalized d20 character trait, such as an ability score or an attack roll, that can be modified by other in-game elements
    /// </summary>
    public abstract class Trait
    {
        #region Private Members
        private Modifiers _modifiers;
        private string _name;
        private int _base;
        private List<ModifierType> _allowedBonusTypes;
        #endregion

        #region Constructor
        /// <summary>
        /// Creates a new instance of a trait and instantiates the name and unmodified value, adds a set of modifiers, and overrides the default allowed bonus types
        /// </summary>
        /// <param name="name">The name of the new trait</param>
        /// <param name="baseValue">The initial unmodified value of the trait</param>
        /// <param name="modifiers">A collection of modifiers to apply to the trait</param>
        /// <param name="allowedBonusTypes">The modifier types that this instance allows instead of the standard</param>
        public Trait(string name, int baseValue, Modifiers modifiers, params ModifierType[] allowedBonusTypes)
        {
            _name = name;
            _base = baseValue;
            _modifiers = modifiers;
            _allowedBonusTypes = new List<ModifierType>(allowedBonusTypes);
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// The name of the trait
        /// </summary>
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
            }
        }

        /// <summary>
        /// The base value of the trait, before any modifiers
        /// </summary>
        public int Base
        {
            get
            {
                return _base;
            }
            set
            {
                _base = value;
            }
        }

        /// <summary>
        /// The total value of the trait including all applicable modifiers
        /// </summary>
        public virtual int Total
        {
            get
            {
                return Base + AllowedModifiers().TotalValue;
            }
        }

        /// <summary>
        /// The modifiers that are being applied to the trait
        /// </summary>
        /// <remarks>
        /// The get accessor returns a shallow copy of the allowed modifiers, so changes to it will not propagate back
        /// unless you later use the set accessor to set the update version.</remarks>
        public Modifiers Modifiers
        {
            get
            {
                return AllowedModifiers();
            }
            set
            {
                ClearModifiers();
                _modifiers = AllowedModifiers(value);
            }
        }

        /// <summary>
        /// The modifier types that can legally modify this trait
        /// </summary>
        public List<ModifierType> AllowedBonusTypes
        {
            get
            {
                return _allowedBonusTypes;
            }
            set
            {
                _allowedBonusTypes = value;
            }
        }
        #endregion

        #region Operators
        /// <summary>
        /// Compares two traits for equality
        /// </summary>
        /// <param name="a">A trait</param>
        /// <param name="b">Another trait</param>
        /// <returns>true if the traits' names, base values, and modifiers are the same, false otherwise</returns>
        public static bool operator ==(Trait a, Trait b)
        {
            if (object.ReferenceEquals(a, null))
            {
                return object.ReferenceEquals(b, null);
            }
            return (string.Equals(a._name, b._name, StringComparison.CurrentCultureIgnoreCase)) && (a._base == b._base) && (a._modifiers == b._modifiers);
        }
        
        /// <summary>
        /// Compares two traits for inequality
        /// </summary>
        /// <param name="a">A trait</param>
        /// <param name="b">Another trait</param>
        /// <returns>false if the traits' names, base values, and modifiers are the same, true otherwise</returns>
        public static bool operator !=(Trait a, Trait b)
        {
            return !(a == b);
        }
        #endregion

        #region Public Instance Methods
        /// <summary>
        /// Adds a modifier to the modifier set of the trait
        /// </summary>
        /// <param name="m">The modifier to add</param>
        /// <remarks>Adds a back-pointer to this trait in the modifier</remarks>
        public void AddModifier(Modifier m)
        {
            if (m != null && this.Allows(m))
            {
                m.TraitModified = this;
                _modifiers.Add(m);
            }
        }

        /// <summary>
        /// Removes all modifiers from the trait's list
        /// </summary>
        /// <remarks>Removes all back-pointers from the former modifiers</remarks>
        public void ClearModifiers()
        {
            foreach (Modifier m in _modifiers)
            {
                m.TraitModified = null;
            }
            _modifiers.Clear();
        }
        
        /// <summary>
        /// Removes the specified modifier from this trait's list
        /// </summary>
        /// <param name="m">The modifier to add</param>
        /// <returns>true if the modifier was removed, false if it was null or not contained in the </returns>
        /// <remarks>Clears the back-pointer to the trait in the modifier</remarks>
        public bool RemoveModifier(Modifier m)
        {
            if (m != null && _modifiers.Remove(m))
            {
                m.TraitModified = null;
                return true;
            }
            return false;
        }

        /// <summary>
        /// A string representation of the trait
        /// </summary>
        /// <returns>The trait name followed by a colon and the total value</returns>
        public override string ToString()
        {
            return string.Format("{0}: {1}", _name, Total);
        }

        /// <summary>
        /// Determines if this trait is equal to a given object
        /// </summary>
        /// <param name="obj">The object to compare to this trait</param>
        /// <returns>True if the object is a trait that has the same name, base value, and modifiers, false otherwise</returns>
        public override bool Equals(object obj)
        {
            Trait t = obj as Trait;
            return this == t;
        }

        /// <summary>
        /// Gives a hash code for the current trait
        /// </summary>
        /// <returns>A hash code</returns>
        public override int GetHashCode()
        {
            return _name.GetHashCode() ^ _base.GetHashCode() ^ _modifiers.GetHashCode();
        }
        #endregion

        #region Private and Protected Utility Methods
        /// <summary>
        /// The subset of the modifiers declared on this trait that have allowed bonus types
        /// </summary>
        /// <returns></returns>
        protected Modifiers AllowedModifiers()
        {
            return AllowedModifiers(_modifiers);
        }

        /// <summary>
        /// The subset of the specified set of modifiers that can apply to this trait
        /// </summary>
        /// <param name="list">The set of modifiers to test</param>
        /// <returns>The subset of the parameter set that can apply to this trait</returns>
        protected Modifiers AllowedModifiers(Modifiers list)
        {
            return new Modifiers(list.Where(new Func<Modifier, bool>(this.Allows)));
        }

        /// <summary>
        /// Determines whether or not a particular modifier can apply to this trait
        /// </summary>
        /// <param name="m">The modifier to test</param>
        /// <returns>True if the modifier is allowed, false otherwise</returns>
        protected virtual bool Allows(Modifier m)
        {
            return _allowedBonusTypes.Contains(m.Type);
        }
        #endregion
    }
}
